#include "queue.h"

#include "../xkit.h"


QueueHeader *QueueInit(losu_vm_t vm, size_t size) {
    QueueHeader *h = (QueueHeader *)__xkit_malloc(sizeof(QueueHeader));
    size ++;
    h->ref = RefInit(vm);
    h->length = size;
    h->headIndex = 0;
    h->tailIndex = 0;
    h->value = (losu_object_t)(__xkit_malloc(sizeof(losu_object) * size));
    return h;
}

void QueueFree(losu_vm_t vm, QueueHeader *h) {
    RefFree(vm, h->ref);
    h->length = 0;
    h->headIndex = 0;
    h->tailIndex = 0;
    __xkit_free(h->value);
    h->value = NULL;
    __xkit_free(h);
}

static size_t tailIndexNext(QueueHeader *h) {
    const size_t index = h->tailIndex + 1;
    return index == h->length ? 0 : index;
}

static size_t headIndexNext(QueueHeader *h) {
    const size_t index = h->headIndex + 1;
    return index == h->length ? 0 : index;
}

static bool isEmpty(QueueHeader *h) {
    if (h->headIndex == h->tailIndex) {
        return true;
    }
    return false;
}

static bool isFull(QueueHeader *h) {
    size_t headIndex = h->headIndex;
    size_t tailIndex = h->tailIndex;
    if (tailIndex == h->length - 1) {
        if (headIndex == 0) {
            return true;
        }
    } else if (tailIndex == headIndex - 1) {
        return true;
    }
    return false;
}

void QueueEnqueue(losu_vm_t vm, QueueHeader *h, losu_object_t o) {
    (void)vm;
    if (!isFull(h)) {
        size_t nextIndex = tailIndexNext(h);
        h->value[h->tailIndex] = *o;
        h->tailIndex = nextIndex;
        RefIncrease(vm, h->ref, o, 1);
    }
}

void QueueDequeue(losu_vm_t vm, QueueHeader *h, losu_object_t o) {
    (void)vm;
    if (!isEmpty(h)) {
        size_t nextIndex = headIndexNext(h);
        *o = h->value[h->headIndex];
        h->headIndex = nextIndex;
        RefDecrease(vm, h->ref, o, 1);
    }
}

void QueuePeek(losu_vm_t vm, QueueHeader *h, losu_object_t o) {
    (void)vm;
    if (!isEmpty(h)) {
        *o = h->value[h->headIndex];
    }
}

bool QueueEmpty(losu_vm_t vm, QueueHeader *h) {
    (void)vm;
    return isEmpty(h);
}

bool QueueFull(losu_vm_t vm, QueueHeader *h) {
    (void)vm;
    return isFull(h);
}

size_t QueueSize(losu_vm_t vm, QueueHeader *h) {
    (void)vm;
    size_t headIndex = h->headIndex;
    size_t tailIndex = h->tailIndex;
    if (headIndex < tailIndex) {
        return tailIndex - headIndex;
    } else if (tailIndex < headIndex) {
        return h->length - headIndex + tailIndex;
    } else {
        return 0;
    }
}
